﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

using HeapModel.ProgInfo;
using HeapModel.ConcreteMemory;
using System.Diagnostics.Contracts;
using HeapModel.RegionPartition;
using System.IO;

namespace HeapModel.ReducedGraph
{
	/// <summary>
	/// This class represents reduced regions in our abstract graph.
	/// These are regions that are further abstracted based on dominance and structural equivalence for the storage similarity relation.
	/// </summary>
	internal sealed class DomReducedNode
	{
		////////////////////////////////////////////////////////////////////////////
		//Member fields that are useful for both child classes

		//id of the node, just a handy label, it can change
		private int m_nid;

		//internal conn stuff
		private List<TypeIdentifier> m_roottypes; //the types that are at the root of the region (i.e. the owner types).
		private List<RegionNode> m_contRegions; //the set of all heap regions that this represents

		//shadow node info
		internal GraphColor NodeColor;
		internal int FinishTime;
		internal bool IsDomSafe;

		/// <summary>
		/// Create a ReducedRegion with the given id but otherwise all null/uninitialized fields
		/// </summary>
		private DomReducedNode(int rid)
		{
			this.m_nid = rid;
			this.m_roottypes = null;
			this.m_contRegions = null;

			this.NodeColor = GraphColor.White;
			this.IsDomSafe = false;
		}

		/// <summary>
		/// Given a regular region equivalence class create a DomReducedNode just containing it.
		/// </summary>
		internal static DomReducedNode InitializeReducedForGivenRegion(RegionNode req)
		{
			Contract.Requires(req != null);
			Contract.Ensures(Contract.Result<DomReducedNode>() != null);

			DomReducedNode rr = new DomReducedNode(req.RegionID);

			rr.m_roottypes = new List<TypeIdentifier>();
			rr.m_roottypes.AddRange(req.RegionTypes);
			rr.m_contRegions = new List<RegionNode>();
			rr.m_contRegions.Add(req);

			rr.NodeColor = GraphColor.White;
			rr.IsDomSafe = false;

			return rr;
		}

		/// <summary>
		/// Use this when joining or to pretty up edge id (so we don't have huge and confusing id numbers).
		/// </summary>
		internal void UpdateNodeID(int newid)
		{
			Contract.Requires(newid >= 0);

			this.m_nid = newid;
		}

		////////////////////////////////////////////////////////////////////////////
		//Query Methods

		internal List<TypeIdentifier> RootRgnTypes { get { return this.m_roottypes; } }
		internal List<RegionNode> ContainedRegions { get { return this.m_contRegions; } }

		public int NodeID { get { return this.m_nid; } }

		/// <summary>
		/// Join all the heap regions into a new summary set.
		/// </summary>
		internal static DomReducedNode JoinNodes(ICollection<DomReducedNode> toMerge, List<TypeIdentifier> roottypes)
		{
			Contract.Requires(toMerge != null);
			Contract.Requires(toMerge.Count >= 2, "Not cool.");
			Contract.Requires(roottypes != null);

			List<RegionNode> nrrs = new List<RegionNode>();
			int minid = Int32.MaxValue;
			int maxfnsh = -1;
			foreach(DomReducedNode hr in toMerge)
			{
				minid = Math.Min(minid, hr.m_nid);
				maxfnsh = Math.Max(maxfnsh, hr.FinishTime);
				nrrs.AddRange(hr.m_contRegions);
			}

			DomReducedNode res = new DomReducedNode(minid);
			res.m_roottypes = new List<TypeIdentifier>(roottypes);
			res.FinishTime = maxfnsh;
			res.m_contRegions = nrrs;

			res.FinishTime = maxfnsh;
			res.IsDomSafe = toMerge.Any((DomReducedNode rr) => rr.IsDomSafe);

			return res;
		}

		////////////////////////////////////////////////////////////////////////////
		//Query for display

		/// <summary>
		/// Dump the reduced node out as DGML.
		/// </summary>
		/// <param name="totalbytesinheap">The total live heap size.</param>
		internal void WriteDgml(int totalbytesinheap, System.Xml.XmlWriter nodes)
		{
			Contract.Requires(nodes != null);

			if(this.m_contRegions.Count > 1)
			{
				String label = "r" + this.m_nid;
				nodes.WriteStartElement("Node");
				nodes.WriteAttributeString("Id", label);

				nodes.WriteStartAttribute("Label");

				//nodes.WriteString("#" + this.NodeID + " ");

				List<TypeIdentifier> idtl = new List<TypeIdentifier>(this.m_roottypes);
				idtl.Sort((TypeIdentifier tid1, TypeIdentifier tid2) => tid1.NameValue.CompareTo(tid2.NameValue));

				nodes.WriteString("{");
				int ctr = 0;
				foreach(TypeIdentifier tt in idtl)
				{
					if(ctr != 0)
						nodes.WriteString(", ");
					++ctr;
					nodes.WriteString(tt.NameValue);

					if(ctr >= 3)
					{
						nodes.WriteString(", ...");
						break;
					}
				}
				nodes.WriteString("}");

				nodes.WriteEndAttribute();

				int thissize = this.m_contRegions.Aggregate<RegionNode, int>(0, (int acc, RegionNode nn) => acc + nn.ComputeTotalBytes());
				nodes.WriteAttributeString("ObjectCount", this.m_contRegions.Aggregate<RegionNode, int>(0, (int acc, RegionNode nn) => acc + nn.ConcreteNodeList.Count).ToString());
				nodes.WriteAttributeString("SizeInBytes", DgmlExtensions.FormatByteTotal(thissize));
				nodes.WriteAttributeString("HeapPercent", ((int)(100 * ((float)thissize) / ((float)totalbytesinheap))).ToString());

				nodes.WriteAttributeString("OnlyReachFromStatic", this.m_contRegions.All((RegionNode nn) => nn.ExtraFacts.NotReachFromLocal).ToString());
				nodes.WriteAttributeString("DataRatio", this.m_contRegions.Min<RegionNode, float>((RegionNode nn) => nn.ExtraFacts.DataToGlueRatio).ToString());

				nodes.WriteStartAttribute("Types");
				bool first = true;
				foreach(TypeIdentifier tt in idtl)
				{
					if(first)
						first = false;
					else
						nodes.WriteString(" ");
					nodes.WriteString(tt.NameValue);
				}
				nodes.WriteEndAttribute();

				nodes.WriteAttributeString("Category", "Summary");
				nodes.WriteAttributeString("Group", "Collapsed");

				nodes.WriteEndElement();
			}
		}
	}
}
